﻿#include <curl/curl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define LIBCURL_EXAM_DEBUG_ENABLE 0

#if LIBCURL_EXAM_DEBUG_ENABLE
#define LIBCURL_EXAM_LOG_E(fmt, args...) fprintf(stderr, "%s %d "fmt, __FUNCTION__, __LINE__, ##args);
#define LIBCURL_EXAM_LOG_I(fmt, args...) fprintf(stderr, "%s %d "fmt, __FUNCTION__, __LINE__, ##args);
#else
#define LIBCURL_EXAM_LOG_E(fmt, args...) fprintf(stderr, "%s %d "fmt, __FUNCTION__, __LINE__, ##args);
#define LIBCURL_EXAM_LOG_I(fmt, args...) 
#endif
#define RESPONDSTRSIZE (16*1024*1024+1)
typedef size_t (*write_callback)(char *, size_t , size_t , void *);

size_t receive_cb(char *buffer, size_t size, size_t nmemb, void *userp) 
{
	char* respond_data = (char*)userp;
	int have_read = strlen(respond_data);
	if (have_read+size*nmemb+1 >= RESPONDSTRSIZE) {
		LIBCURL_EXAM_LOG_E("RESPONDSTRSIZE(%d) is too small to hold respond, please increse RESPONDSTRSIZE\n", RESPONDSTRSIZE);
		return 0;
	}
	memcpy(respond_data+have_read, buffer, size*nmemb);
	/*如果不是返回size乘以nmemb，libcurl将会认为出错*/
	return size*nmemb;
}  

/*
 *http返回的数据放在respond_data中。respond_data数组的空间由调用者申请，需要足够大来存放整个respond
 */
int generate_curl(CURL **_curl, const char* url, char* postfield, write_callback cb, char* respond_data, int timeout)
{
	int ret = 0;
	CURL *curl_tmp = NULL; 
	curl_tmp = curl_easy_init();
	if (!curl_tmp) {
		LIBCURL_EXAM_LOG_E("curl_easy_init failed\n");
		ret = -1;
		goto out;
	}
	if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_URL, url)) != CURLE_OK) {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_URL failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}
	if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_WRITEFUNCTION, receive_cb)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_WRITEFUNCTION failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}  
	if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_TIMEOUT, timeout)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_TIMEOUT failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}  
	if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_WRITEDATA, respond_data)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_WRITEDATA failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	} 
	if (postfield) {
		if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_POST, 1)) != CURLE_OK)  {
			LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_POST failed:%s\n", curl_easy_strerror(ret));
			ret = -1;
			goto out;
		}	 
		if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_COPYPOSTFIELDS , postfield)) != CURLE_OK) {
			LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_COPYPOSTFIELDS failed:%s\n", curl_easy_strerror(ret));
			ret = -1;
			goto out;
		} 
	}
	 if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_VERBOSE, 1)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_VERBOSE failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}
	if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_IPRESOLVE failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}
	 /*禁用reuse socket，不禁用的话socket不会马上释放，数量多的时候浪费资源*/
	 if ( (ret = curl_easy_setopt(curl_tmp, CURLOPT_FORBID_REUSE, 1)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_FORBID_REUSE failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}
	/* 禁用alarm 实现域名解析,选择c-ares,否则有概率出现域名解析失败时程序崩溃 */
	if((curl_easy_setopt(curl_tmp, CURLOPT_NOSIGNAL, 1L)) != CURLE_OK)  {
		LIBCURL_EXAM_LOG_E("curl_easy_setopt CURLOPT_FORBID_REUSE failed:%s\n", curl_easy_strerror(ret));
		ret = -1;
		goto out;
	}

out:
	if (ret < 0) {
    	curl_easy_cleanup(curl_tmp);
		curl_tmp = NULL;
	}
	*_curl = curl_tmp;
	return ret;
}


int do_request(CURL *curl)
{
	int res = 0;
	if ( (res = curl_easy_perform(curl)) != CURLE_OK) {
		LIBCURL_EXAM_LOG_E("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
		res = -1;
		goto out;
	}
out:
	return res;
}
int http_request(const char* url, int timeout)
{
	int ret = 0, global_flag = 0;
	CURL *curl = NULL;
	char* data = NULL;
	data = calloc(RESPONDSTRSIZE, 1);
	if (!data) {
		LIBCURL_EXAM_LOG_E("http request failed: calloc %d size failed\n", RESPONDSTRSIZE);
		ret = -1;
		goto out;
	}
	if ( (ret = curl_global_init(CURL_GLOBAL_ALL)) != CURLE_OK) {
		LIBCURL_EXAM_LOG_E("http request failed: curl_global_init error\n");
		ret = -1;
		goto out;
	}
	global_flag = 1;
	if (generate_curl(&curl, url, NULL, receive_cb, data, timeout) < 0) {
		LIBCURL_EXAM_LOG_E("http request failed: cannot gengerate a request\n");
		ret = -1;
		goto out;
	}
	if (do_request(curl) < 0) {
		LIBCURL_EXAM_LOG_E("http request failed\n");
		ret = -1;
		goto out;
	}
	fprintf(stdout, "request succeed, respond is:%s\n", data);
out:
	if (data)
		free(data);
	if (curl)
		curl_easy_cleanup(curl);
	if (global_flag)
		curl_global_cleanup();
	return ret;
}

int main(int argc, char** argv)
{
	if (argc < 2) {
		fprintf(stderr, "error\n");
		return -1;
	}
	fprintf(stdout, "requst url:%s\n", argv[1]);	
	http_request(argv[1], 5);
	return 0;
}
